package com.fm.parser.csv;

import com.fm.base.annotation.UnThreadSafe;
import com.fm.base.factory.ClassFactory;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.*;

/**
 * 每条记录占一行
 * 以逗号为分隔符
 * 逗号前后的空格会被忽略
 * 字段中包含有逗号，该字段必须用双引号括起来
 * 字段中包含有换行符，该字段必须用双引号括起来
 * 字段前后包含有空格，该字段必须用双引号括起来
 * 字段中如果有双引号，该字段必须用双引号括起来
 * 字段中的双引号用两个双引号表示
 * 第一条记录，可以是字段名
 */
@UnThreadSafe
public class CsvReader {
	private List<String> headers;
	private boolean headersInCsv;
	private int rowNum;
	private boolean quoted;
	private int previous;
	private int index;
	private int curIndex;

	private void setValue(StringBuilder value, Map<String, String> map) {
		if (!StringUtils.isEmpty(value.toString())) {
			if (rowNum == 1 && headersInCsv) {
				headers.add(value.toString());
			} else {
				map.put(headers.get(index++), value.toString());
			}
		} else {
			if (rowNum == 1 && headersInCsv) {
				throw new RuntimeException("header有空的情况");
			} else {
				map.put(headers.get(index++), null);
			}
		}
		value.setLength(0);
	}

	private int read(Reader reader) throws IOException {
		++curIndex;
		return reader.read();
	}

	public void doParse(String filename, CsvRowReader<Map<String, String>> reader) {
		doParse(filename, "utf-8", reader);
	}

	public void doParse(String filename, String charset, CsvRowReader<Map<String, String>> reader) {
		try {
			doParse(new BufferedReader(new InputStreamReader(new FileInputStream(filename), charset)), null, reader, LinkedHashMap::new);
		} catch (FileNotFoundException | UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	public void doParse(InputStream in, CsvRowReader<Map<String, String>> reader) {
		doParse(new InputStreamReader(in), null, reader, LinkedHashMap::new);
	}

	public <T extends Map<String, String>> void doParse(Reader streamReader, List<String> headers,
			CsvRowReader<T> reader, ClassFactory<T> factory) {
		// 初始化
		rowNum = 1;
		previous = -1;
		quoted = false;
		index = 0;
		curIndex = -1;
		if (headers == null) {
			headersInCsv = true;
			this.headers = new ArrayList<>();
		} else {
			headersInCsv = false;
			this.headers = headers;
		}

		try {
			doParse(streamReader, ',', '"', reader, factory);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @param reader    读取器
	 * @param spliter   分割字符 ',' ';' '\t'
	 * @param quotation 引号字符 '"'
	 * @throws IOException if reader read has some problems
	 */
	private <T extends Map<String, String>> void doParse(Reader streamReader, Character spliter,
			Character quotation, CsvRowReader<T> reader, ClassFactory<T> factory) throws IOException {
		StringBuilder buffer = new StringBuilder();
		int current;
		T map = factory.create();
		int spaceCount = 0;

		while (buffer != null) {
			current = read(streamReader);
			if (current == -1) {
				if (buffer.length() > 0 || map.size() > 0) {// 这不是一个空行
					setValue(buffer, map);
					reader.doRow(rowNum, map);
				}
				streamReader.close();
				buffer = null;
			} else if (current == quotation) {
				if (quoted) { // 在引号里
					if (previous == quotation) {
						buffer.append((char) current);
						previous = -1;
					} else {
						previous = quotation;
					}
				} else { // 不在引号里
					if (buffer.length() == 0) {
						quoted = true;
					} else if (buffer.length() == spaceCount) {// 清除掉不必要的引号
						buffer.setLength(0);
						quoted = true;
					} else {
						throw new RuntimeException("不合理的引号位于: " + curIndex);
					}
					previous = -1;
				}
			} else if (current == ' ') {
				if (quoted) {
					if (previous == quotation) {
						quoted = false;
						setValue(buffer, map);
					} else {
						buffer.append((char) current);
					}
				} else {
					buffer.append((char) current);
					++spaceCount;
				}
				previous = current;
			} else if (current == spliter) {
				if (quoted) {
					if (previous == quotation) {
						quoted = false;
						setValue(buffer, map);
					} else {
						buffer.append((char) current);
					}
				} else {
					setValue(buffer, map);
				}
				previous = current;
			} else if (current == '\r') {
				if (quoted) {
					if (previous == quotation) {
						quoted = false;
						setValue(buffer, map);
					}
				}
				previous = current;
			} else if (current == '\n') {
				if (quoted) {
					quoted = false;
				}
				if (buffer.length() != 0) {
					setValue(buffer, map);
				}
				if (!headersInCsv || rowNum != 1) {
					reader.doRow(rowNum, map);
					map = factory.create();
				}
				++rowNum;
				index = 0;
			} else {
				buffer.append((char) current);
			}
		}
	}
}
